/*****************************************************************
|
mp4_atomcaching_server.cpp
__author __ : nguyentiennam2910@gmail.com
|
 ****************************************************************/

/*----------------------------------------------------------------------
|   includes
+---------------------------------------------------------------------*/
#include "Ap4.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/inotify.h>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <signal.h>
#include <sys/file.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <ctime>
using namespace std;

#define EVENT_SIZE  ( sizeof (struct inotify_event) )
#define BUF_LEN	 ( 1024 * ( EVENT_SIZE + 16 ) )
#define PID_FILE_LOG	"/home/lv_centos/Desktop/mp4_log/mp4_atomcaching.pid"
#define MP4_DIRECTORY_MONITOR "/home/lv_centos/Desktop/mp4/"
#define HTTP_DIRECTORY "/var/www/html/"
#define LOG_FILE "/home/lv_centos/Desktop/mp4_log/mp4_atomcaching.log"
#define SIZE_QUEUE 1000

struct cmd{
	char input_filename[300];
	char moov_output[300];
	char mdat_output[300];
	
};
struct queue_struct{
	cmd command[SIZE_QUEUE];
	int front;
	int rear;
};
void initQueue(queue_struct *q){
	q->front = -1;
	q->rear = -1;
}
int isEmpty(queue_struct *q){
	if (q->front==-1) return 1;
	return 0;
}
int isFull(queue_struct q){
	return (q.rear-q.front+1)%SIZE_QUEUE ==0;
}
void push(cmd *c, queue_struct *q){
	
	if(isEmpty(q)) q->front = 0;
	q->rear = (q->rear+1)%SIZE_QUEUE;
	q->command[q->rear] = *c; 
}
cmd* pop(queue_struct *q){
	int i;
	if(!isEmpty(q)){
		if(q->front==q->rear){
			i = q->front;
			q->front = -1;
			q->rear = -1;
			return &(q->command[i]);
		}else{
		q->front=(q->front+1)%SIZE_QUEUE;
		return &(q->command[q->front-1]);
		}
	}
	else{
		return NULL;	
	}
}
//No pop
cmd* front(queue_struct *q){
	if(!isEmpty(q)){
		return &(q->command[q->front]);
	}
	else
		return NULL;
}
void termination_handler(int signum){
	unlink(PID_FILE_LOG);
	exit(0);
}
int mp4_atomspilt(cmd *cmd1){
	struct cmd* cmd2 = (struct cmd*) cmd1;
	FILE *f;
	f=fopen(LOG_FILE,"a");
	time_t t = time(0);   // get time now
    struct tm * now = localtime( & t );
	
	// parse arguments
	char* moov_output  = NULL;
	char* mdat_output = NULL;
	char* input_filename = NULL;
	moov_output = (char *) malloc(300); 
	mdat_output = (char *) malloc(300); 
	input_filename = (char *) malloc(300);
	strcpy(input_filename, cmd2->input_filename);
	strcpy(moov_output, cmd2->moov_output);
	strcpy(mdat_output, cmd2->mdat_output);
	// create the input stream
	AP4_Result result;
	AP4_ByteStream* input = NULL;
	AP4_Result result_fytp;
	AP4_ByteStream* input_fytp = NULL;
	AP4_Result result_mdat;
	AP4_ByteStream* input_mdat = NULL;
	fprintf(f,"%d-%d-%d:%d-%d-%d: New input file %s\n",(now->tm_year + 1900),(now->tm_mon + 1),
			now->tm_mday,now->tm_hour,now->tm_min,now->tm_sec,input_filename);
	result = AP4_FileByteStream::Create(input_filename, AP4_FileByteStream::STREAM_MODE_READ, input);
	if (AP4_FAILED(result)) {
		//fprintf(stderr, "Warning: cannot open input file (%s)\n", input_filename);
		return 1;
	}
	// parse the atoms
	AP4_AtomParent top_level_moov;
	AP4_Atom* atom_moov;
	AP4_AtomFactory& atom_factory = AP4_DefaultAtomFactory::Instance;
	while (atom_factory.CreateAtomFromStream(*input, atom_moov) == AP4_SUCCESS) {
		top_level_moov.AddChild(atom_moov);
	}
	input->Release();
	// atom fytp --- Nam
	result_fytp = AP4_FileByteStream::Create(input_filename, AP4_FileByteStream::STREAM_MODE_READ, input_fytp);
	if (AP4_FAILED(result)) {
		//fprintf(stderr, "Warning: cannot open input file (%s)\n", input_filename);
		return 1;
	}
	result_mdat = AP4_FileByteStream::Create(input_filename, AP4_FileByteStream::STREAM_MODE_READ, input_mdat);
	if (AP4_FAILED(result_mdat)) {
		//fprintf(stderr, "Warning: cannot open input file (%s)\n", input_filename);
		return 1;
	}
	AP4_AtomParent top_level_fytp;
	AP4_Atom* atom_fytp;
	AP4_AtomParent top_level_mdat;
	AP4_Atom* atom_mdat;
	AP4_AtomFactory& atom_factory_fytp = AP4_DefaultAtomFactory::Instance;
	while (atom_factory_fytp.CreateAtomFromStream(*input_fytp, atom_fytp) == AP4_SUCCESS) {
		top_level_fytp.AddChild(atom_fytp);
	}
	AP4_AtomFactory& atom_factory_mdat = AP4_DefaultAtomFactory::Instance;
	while (atom_factory_mdat.CreateAtomFromStream(*input_mdat, atom_mdat) == AP4_SUCCESS) {
		top_level_mdat.AddChild(atom_mdat);
	}
	// release the input
	input_mdat->Release();
	// find the atom
	atom_moov = top_level_moov.FindChild("moov");
	if (atom_moov == NULL) {
		//fprintf(stderr, "Warning: atom moov not found\n");
		return 1;
	}
	// atom fytp
	atom_fytp = top_level_fytp.FindChild("ftyp");
	if (atom_fytp == NULL) {
		//fprintf(stderr, "Warning: atom 'fytp' not found\n");
		return 1;
	}
	// atom mdat
	atom_mdat = top_level_mdat.FindChild("mdat");
	if (atom_mdat == NULL) {
		//fprintf(stderr, "Warning: atom 'mdat' not found\n");
		return 1;
	}
	// create the output stream
	AP4_ByteStream* output_stream_moov = NULL;
	//create the output stream
	AP4_ByteStream* output_stream_mdat = NULL;
	//Write moov atom + fytp atom to file
	result = AP4_FileByteStream::Create(moov_output, AP4_FileByteStream::STREAM_MODE_WRITE, output_stream_moov);
	if (AP4_FAILED(result)) {
		//fprintf(stderr, "Warning: cannot open output file (%s)\n", moov_output);
		return 1;
	}
	result = AP4_FileByteStream::Create(mdat_output, AP4_FileByteStream::STREAM_MODE_WRITE, output_stream_mdat);
	if (AP4_FAILED(result)) {
		//fprintf(stderr, "Warning: cannot open output file (%s)\n", mdat_output);
		return 1;
	}
	// write the atom
	atom_fytp->Write(*output_stream_moov);
	atom_moov->Write(*output_stream_moov);
	output_stream_moov->Release();
	// write mdat atom to file
	atom_mdat->Write(*output_stream_mdat);
	// cleanup
	output_stream_mdat->Release();
	t = time(0);   // get time now
    now = localtime( & t );
	fprintf(f,"%d-%d-%d:%d-%d-%d: Write file %s success\n",(now->tm_year + 1900),(now->tm_mon + 1),
			now->tm_mday,now->tm_hour,now->tm_min,now->tm_sec,moov_output);
	fprintf(f,"%d-%d-%d:%d-%d-%d: Write file %s success\n",(now->tm_year + 1900),(now->tm_mon + 1),
			now->tm_mday,now->tm_hour,now->tm_min,now->tm_sec,mdat_output);
	fclose(f);
	return 0;
}
/*----------------------------------------------------------------------
|   main
+---------------------------------------------------------------------*/
int main(int argc, char** argv)
{
	FILE *f;
	pid_t pid, sid;
	int length, i = 0, j;
	int fd;
	int wd;
	int shmid;
	//creat share memory
	queue_struct *work_queue = (queue_struct *) malloc(sizeof(queue_struct));
	shmid = shmget(IPC_PRIVATE, sizeof(queue_struct), IPC_CREAT|0666);
	work_queue = (queue_struct *) shmat(shmid, 0, 0);
	//init queue sharememory
	initQueue(work_queue);
	f = fopen(PID_FILE_LOG,"r");
	int pid_file= open(PID_FILE_LOG, O_CREAT | O_RDWR, 0666);
	int rc = flock(pid_file, LOCK_EX | LOCK_NB);
	if(!rc){
	  	char buffer[BUF_LEN];
		fd = inotify_init();
		if ( fd < 0 ) {
			perror( "inotify_init" );
			}
		wd = inotify_add_watch( fd, MP4_DIRECTORY_MONITOR, 
								IN_ALL_EVENTS);  
		/* Fork process monitor directory */
		pid = fork();	
		// can't fork process
		if (pid < 0) {
			exit(EXIT_FAILURE);
		}
		//main process			
		if (pid > 0) {
		//fork new process to split mp4 file
			pid = fork();
			// can't fork process
			if (pid < 0) {
				exit(EXIT_FAILURE);
			}
			// creat new process success, exit main process
			if (pid > 0) {
				exit(EXIT_SUCCESS);
			}else{
			/*code for process splitfile*/
			//save pid
				//char buffer1[5];
				//memset(buffer,'\0',5);
				//sprintf(buffer1,"%d",pid);
				//write(pid_file,buffer1,strlen(buffer1));
				while(1){
					if(isEmpty(work_queue) == 0){
						sleep(1);
						struct cmd *cmd1 = (cmd *) malloc(sizeof(cmd));
						cmd1 = front(work_queue);
						if( mp4_atomspilt(cmd1) == 1){
							sleep(3);
						}else
						pop(work_queue);
						//shmdt((void *)work_queue);
						shmctl(shmid, IPC_RMID, NULL);
						
			
					}else{
						sleep(1);
					}
				}
			}
			umask(0);		 
			/* Open any logs here */			  
			/* Create a new SID for the splitfile process */
			sid = setsid();
			if (sid < 0) {
				/* Log the failure */
				exit(EXIT_FAILURE);
			}
			/* Change the current working directory */
			if ((chdir("/")) < 0) {
				/* Log the failure */
				exit(EXIT_FAILURE);
			}
			/* Close out the standard file descriptors */
			if(signal(SIGINT,termination_handler) == SIG_IGN){
			//#close(pid_file);
				signal(SIGINT, SIG_IGN);
			}
			if(signal(SIGHUP,termination_handler) == SIG_IGN){
			//#close(pid_file);	
				signal(SIGHUP, SIG_IGN);
			}
			if(signal(SIGTERM,termination_handler) == SIG_IGN){
				//#close(pid_file);
				signal(SIGTERM, SIG_IGN);

			}
			close(STDIN_FILENO);
			close(STDOUT_FILENO);
			close(STDERR_FILENO);
			/* Daemon-specific initialization goes here */
		}
			/*code for process monitor*/
			umask(0);	
			/* Open any logs here */			
			/* Create a new SID for the child process */
			sid = setsid();
			if (sid < 0) {
				/* Log the failure */
				exit(EXIT_FAILURE);
			}
			/* Change the current working directory */
			if ((chdir("/")) < 0) {
				/* Log the failure */
				exit(EXIT_FAILURE);
			}
			/* Close out the standard file descriptors */
			if(signal(SIGINT,termination_handler) == SIG_IGN){
				//#close(pid_file);
				signal(SIGINT, SIG_IGN);
				
			}
			if(signal(SIGHUP,termination_handler) == SIG_IGN){
			//#close(pid_file);	
			signal(SIGHUP, SIG_IGN);
			}
			if(signal(SIGTERM,termination_handler) == SIG_IGN){
				//#close(pid_file);
				signal(SIGTERM, SIG_IGN);

			}
			close(STDIN_FILENO);
			close(STDOUT_FILENO);
			close(STDERR_FILENO);
			/* Daemon-specific initialization goes here */
			/* The Big Loop */
			while (1) {
				for (;;) {
					i = 0;
					length = read( fd, buffer, BUF_LEN );		  
					if ( length < 0 ){
						perror( "read" );
					}
					struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
					if ( event->len ){
						if ( event->mask & IN_CREATE) {
							if ( !(event->mask & IN_ISDIR) ){
								struct cmd *cmd1 = (cmd *) malloc(sizeof(cmd));
								memset(cmd1->input_filename,'\0',300);
								memset(cmd1->moov_output,'\0',300);
								memset(cmd1->mdat_output,'\0',300);

								strcpy(cmd1->input_filename,MP4_DIRECTORY_MONITOR);
								strcat(cmd1->input_filename,event->name);
								j =strlen(event->name)-1;
								while(event->name[j]!='.') j--;
								strcat(cmd1->moov_output, HTTP_DIRECTORY);
								strncat(cmd1->moov_output, event->name,j);
								strcat(cmd1->moov_output,"-1.mp4");
								strcat(cmd1->mdat_output, HTTP_DIRECTORY);
								strncat(cmd1->mdat_output, event->name,j);
								strcat(cmd1->mdat_output,"-2.mp4");
								push(cmd1,work_queue);	
								break;	
							}
						}
					}
				}
			}	
			( void ) inotify_rm_watch( fd, wd );
			( void ) close( fd );
			exit(EXIT_SUCCESS);
	}else{	
		printf ("Service mp4_atomcaching_server running\n");
		exit(EXIT_SUCCESS);
	}
	
}

